﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using System.Xml;
using System.Xml.Linq;
using System.Text;
using Microsoft.Xna.Framework.GamerServices;
using System.IO;
#if WINDOWS_PHONE
using System.IO.IsolatedStorage;
#endif
using GarageGames.Torque.XNA;
using System.Threading;

#if !WINDOWS_PHONE
using Microsoft.Xna.Framework.Storage;
#endif

namespace GarageGames.Torque.Storage
{
#if WINDOWS_PHONE

    public enum StorageReturnStatus
    {
    Ok = 0
    }
    public sealed class StorageManager
    {
        public static StorageManager Instance
        {
            get
            {
                if (_instance == null)
                    _instance = new StorageManager();

                return _instance;
            }
        }

        /// <summary>
        /// This setter is the one that we need to call as first thing
        /// to have the Storage Manager initialized. 
        /// Each game should use only one main folder.
        /// </summary>
        /// <param name="storageName">This is the name of the main data folder. One folder per game is enough.</param>
        /// <returns></returns>
        public StorageReturnStatus SetStorageName(string storageName)
        {
            _storageName = storageName;

            return StorageReturnStatus.Ok;
        }

        /// <summary>
        /// This method takes care of de-serializing the data. You have to pass here 
        /// the type of data you expect to read.
        /// </summary>
        /// <param name="typeOfObject">The type of data you want to de-serialize</param>
        /// <param name="fileName">The file name to read to de-serialize the data</param>
        /// <returns>The de-serialized data</returns>
        public Object ReadData(Type typeOfObject, string fileName)
        {
            Object ret = new Object();
            TextReader reader = null;
            try
            {
                IsolatedStorageFile isoStorage = IsolatedStorageFile.GetUserStoreForApplication();
                IsolatedStorageFileStream file = isoStorage.OpenFile(fileName, FileMode.OpenOrCreate);
                reader = new StreamReader(file);


                
                XmlSerializer xs = new XmlSerializer(typeOfObject);
                ret = xs.Deserialize(reader);
                reader.Close();
                if (reader != null)
                    reader.Dispose();
            }
            catch
            {
            }
            return ret;
            
        }

         /// <summary>
        /// This method takes care of serializing the data. You have to pass here 
        /// the actual class (or list) containing the data to be serialized.
        /// The data will be saved using the file name.
        /// </summary>
        /// <param name="dataToSerialize">Instance of the data to be serialized</param>
        /// <param name="fileName">The file name to use to serialize the data</param>
        /// <returns>The call status</returns>
        public StorageReturnStatus SaveData(Object dataToSerialize, string fileName)
        {
            TextWriter writer;
            IsolatedStorageFile isoStorage = IsolatedStorageFile.GetUserStoreForApplication();
            IsolatedStorageFileStream file = isoStorage.OpenFile(fileName, FileMode.Create);
            writer = new StreamWriter(file);
            XmlSerializer xs = new XmlSerializer(dataToSerialize.GetType());
            xs.Serialize(writer, dataToSerialize);
            writer.Close();
            return StorageReturnStatus.Ok;
        }

        private static StorageManager _instance = null;
        private string _storageName = String.Empty;
    }
#endif
#if !WINDOWS_PHONE
    public enum StorageReturnStatus
    {
        Ok = 0,
        StorageNameNotSetYet,
        GuideIsVisible,
        UserDidNotSelectTheStorage,
        HardwareOrPermissionError,
        Selecting,
        UnknownError,
       OpenContainerPending,
        StorageNameAlreadySet
    }

    /// <summary>
    /// Singletone manager for the XNA Storage system
    /// </summary>
    public sealed class StorageManager
    {
        static StorageManager _instance = null;
        bool _storageIsValid = false;
        string _storageName = String.Empty;
        StorageContainer _container = null;
        StorageDevice _device = null;
        // Used to recover failures
        Object _dataWaiting = null;
        string _fileWaiting = String.Empty;
        //these two booleans are needed to prevent concurrently executing operations, which creates crashes.
        bool _selectionPending = false;
        bool _openPending = false;

        public static StorageManager Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new StorageManager();
                    bool Ok = false;
                    foreach (Microsoft.Xna.Framework.GameComponent c in TorqueEngineComponent.Instance.Game.Components)
                    {
                        if (c is GamerServicesComponent)
                            Ok = true;
                    }
                    if (!Ok)
                    {
                        GamerServicesComponent gsc = new GamerServicesComponent(TorqueEngineComponent.Instance.Game);
                        TorqueEngineComponent.Instance.Game.Components.Add(gsc) ;
                        gsc.Initialize();
                    }
                    StorageDevice.DeviceChanged += new EventHandler<EventArgs>(_instance.StorageDevice_DeviceChanged);
                }
                return _instance;
            }
        }

        private void StorageDevice_DeviceChanged(object sender, EventArgs e)
        {
            InvalidateTheStorage();
        }

        /// <summary>
        /// This setter is the one that we need to call as first thing
        /// to have the Storage Manager initialized. 
        /// Each game should use only one main folder.
        /// </summary>
        /// <param name="storageName">This is the name of the main data folder. One folder per game is enough.</param>
        /// <returns></returns>
        public StorageReturnStatus SetStorageName(string storageName)
        {
            if (_storageName != String.Empty)
                return StorageReturnStatus.StorageNameAlreadySet;
            _storageName = storageName;

            try
            {
                InvalidateTheStorage();
                _selectionPending = true;
                StorageDevice.BeginShowSelector(SelectorEnd, null);
            }
            catch { } // The user has another Guide component visible: we don't care at this stage

            // Not really needed a return status. It's here for future enhancements
            return StorageReturnStatus.Ok;
        }


        private void SelectorEnd(IAsyncResult ar)
        {
            _selectionPending = false;
            _device = StorageDevice.EndShowSelector(ar);
            _storageIsValid = (_device != null && _device.IsConnected);
            if (_storageIsValid)
            {
                try
                {
                    if (_container != null)
                        _container = null;
                    _openPending = true;
                    _device.BeginOpenContainer(_storageName, containerOpened, _dataWaiting);

                }
                //If we enter here we have a serious problem
                catch (Exception ex)
                {
                    InvalidateTheStorage();
                }
            }

        }

        private void containerOpened(IAsyncResult ar)
        {
            _openPending = false;
            _container = _device.EndOpenContainer(ar);
            if (_dataWaiting != null)
            {
                SaveData(_dataWaiting, _fileWaiting);
            }
        }


        /// <summary>
        /// This method takes care of serializing the data. You have to pass here 
        /// the actual class (or list) containing the data to be serialized.
        /// The data will be saved using the file name.
        /// </summary>
        /// <param name="dataToSerialize">Instance of the data to be serialized</param>
        /// <param name="fileName">The file name to use to serialize the data</param>
        /// <returns>The call status</returns>
        public StorageReturnStatus SaveData(Object dataToSerialize, string fileName)
        {
            // throw new Exception("StorageManager.cs: This method not yet implemented"); //willxisxveganx@gmail.com
            if (_selectionPending || _openPending)
                return StorageReturnStatus.Selecting;
            _dataWaiting = null;
            _fileWaiting = String.Empty;
            if (_storageName == String.Empty)
                return StorageReturnStatus.StorageNameNotSetYet;
            if (!_storageIsValid)
            {

#if XBOX360
                if (Guide.IsVisible)
                    return StorageReturnStatus.GuideIsVisible;
#endif
                IAsyncResult ar;
                try
                {
                    InvalidateTheStorage();
                    _dataWaiting = dataToSerialize;
                    _fileWaiting = fileName;
                    _selectionPending = true;
                    ar = StorageDevice.BeginShowSelector(SelectorEnd, null);
                    return StorageReturnStatus.Selecting;
                }
                catch
                {
                    // Sanity: the Guide was visible
                    return StorageReturnStatus.GuideIsVisible;
                }
            }
            // Sanity
            if (!_storageIsValid)
                return StorageReturnStatus.UnknownError;
            string path = Path.Combine(_storageName, fileName);
            try
            {
                if (_container.FileExists(fileName))
                    _container.DeleteFile(fileName);

                Stream stream = _container.CreateFile(fileName);
                XmlSerializer serializer = new XmlSerializer(dataToSerialize.GetType());
                serializer.Serialize(stream, dataToSerialize);
                stream.Close();
                InvalidateTheStorage();
            }
            // This should be more specific
            catch
            {
                return StorageReturnStatus.UnknownError;
            }
            return StorageReturnStatus.Ok;

        }

        /// <summary>
        /// Flag to check to know if there is a valid Storage Device
        /// </summary>
        public bool StorageIsValid
        {
            get
            {
                return _storageIsValid;
            }
        }

        /// <summary>
        /// This method takes care of de-serializing the data. You have to pass here 
        /// the type of data you expect to read.
        /// </summary>
        /// <param name="typeOfObject">The type of data you want to de-serialize</param>
        /// <param name="fileName">The file name to read to de-serialize the data</param>
        /// <returns>The de-serialized data</returns>
        public Object ReadData(Type typeOfObject, string fileName)
        {
            // throw new Exception("StorageManager.cs: This method not yet implemented"); //willxisxveganx@gmail.com
            if (_selectionPending || _openPending)
                return null;
            if (_storageName == String.Empty)
                return null;//need to set name before reading data
            if (!_storageIsValid)
            {
#if XBOX360
                if (Guide.IsVisible)
                    return null;
#endif
                IAsyncResult ar;
                try
                {
                    InvalidateTheStorage();
                    _selectionPending = true;
                    ar = StorageDevice.BeginShowSelector(SelectorEnd, null);
                    int sleeps = 0;
                    while (!_storageIsValid)//Loop added by Will Regan to compenstate for new Asynchronous loading.
                    {
                        Thread.Sleep(1000);
                        sleeps++;
                        if (sleeps > 5)
                            return null;//took longer then 5 seconds, maybe we should quit.
                    }
                }
                catch
                {
                    // Sanity: the Guide was visible
                    return null;
                }
            }
            // Sanity
            if (!_storageIsValid)
                return null;
            string path = Path.Combine(_storageName, fileName);
            object ret = null;
            try
            {
                if (!_container.FileExists(fileName))
                {
                    InvalidateTheStorage();
                    return null;
                }
                Stream stream = _container.OpenFile(fileName, FileMode.Open);
                XmlSerializer serializer = new XmlSerializer(typeOfObject);
                ret = serializer.Deserialize(stream);
                stream.Close();
                InvalidateTheStorage();
            }
            // This should be more specific
            catch
            {
                return null;
            }
            return ret;

        }

        private void InvalidateTheStorage()
        {
            if (_container != null && !_container.IsDisposed)
                _container.Dispose();
            _device = null;
            _container = null;
            _storageIsValid = false;
        }
        /// <summary>
        /// This is here just to hide the constructor to the user
        /// </summary>
        private StorageManager()
        {
        }
    }
#endif
}
